/**
 * Clase Proxy: Lee el HTTP request del cliente y determina si está en la lista
 * de páginas prohibidas. De ser así, le responde con una página indicándole
 * dicha situación. En caso contrario, procede a realizar la conexión con
 * el servidor.
 *
 * Autores: Christian Chomiak (05-38034)
 *          Miguel De Los Ríos (05-38084)
 */
package kraken;

import java.net.*;
import java.util.*;
import java.io.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;


public class Proxy implements Runnable {

    // Variables para el cliente
    private Socket client_socket = null;
    private Hashtable<String, List<String>> prohibidas;
    private BufferedInputStream entrada;
    private OutputStream salida;
    private String self_ip;

    // Variables para el servidor
    private Socket server_socket;
    private String server_ip;
    private URL server_url;

    public Proxy(Socket client_socket, Hashtable<String, List<String>> prohibidas) {
        this.client_socket = client_socket;
        this.prohibidas = prohibidas;
        try {
            this.entrada = new BufferedInputStream(client_socket.getInputStream());
            this.salida = client_socket.getOutputStream();
            self_ip = InetAddress.getLocalHost().getHostAddress();
        } catch (IOException e) {
            System.err.println("Error leyendo el Request HTTP");
            System.exit(1);
        }
    }

    /*
     * Función LeerRequest(): Se encarga de leer el HTTP request. Retorna un string
     * con el mismo
     */
    public synchronized String LeerRequest() {
        StringBuilder req = new StringBuilder(8096);
        try {
            int buf_size = 1024;
            byte[] buffer = new byte[buf_size];
            int input = 0;
            while (((buf_size = entrada.available()) > 0) && input >= 0) {
                buffer = new byte[buf_size];
                input = entrada.read(buffer);
                req.append(new String(buffer));
            }
        } catch (IOException ex) {
            System.err.println("Error en la lectura del request");
            ex.printStackTrace();
        }
        return req.toString();
    }
    
    /*
     * Función BuscarEnProhibidas(): Procesa el request y busca si esta en la lista de
     * páginas prohibidas. Retorna true si esta prohibida, false en caso contrario.
     */
    public boolean BuscarEnProhibidas(String request) {
        int desde;
        int hasta;

        desde = request.indexOf(" ");
        hasta = request.indexOf(" ", (desde + 1));

        //Obtiene el url completo
        String url;
        try {
            url = request.substring(desde, hasta).trim();
        } catch (StringIndexOutOfBoundsException e) {
            server_ip = null;
            return true;
        }
        //System.out.println("url: " + url);

        /*
         * Verifica que el URL sea valido, obtiene el host y el path (que son los
         * que se necesitan para saber si está prohibido el sitio) y busca
         * en el Hashtable.
         */
        try {
            server_url = new URL(url);
            String host = server_url.getHost();
            String path = server_url.getPath();
            // Si el ultimo caracter del path no es un /, se coloca uno.
            if (path.lastIndexOf("/") != (path.length() - 1)) {
                path += "/";
            }
            // Obtengo el IP del host
            String servidor = InetAddress.getByName(host).toString();
            servidor = servidor.substring(servidor.indexOf("/") + 1);
            server_ip = servidor;
            if (prohibidas.containsKey(servidor)) {
                //Busco en el subdirectorio a ver si está prohibido
                List<String> subdirectorios = prohibidas.get(servidor);
                for (int i = 0; i < subdirectorios.size(); i++) {
                    if (path.equals("/")) {
                        if (path.equals(subdirectorios.get(i))) {
                            return true;
                        }
                    } else {
                        String subdir = subdirectorios.get(i);
                        if (path.startsWith(subdir) && !(subdir.equals("/"))) {
                            return true;
                        }
                    }
                }
                return false;
            } else {
                return false;
            }
        } catch (Exception e) {
        }
        return false;
    }

    /*
     * Función getDate(): Obtiene la fecha actual y la convierte a string
     * para ser copiada en el log.
     */
    public String getDate() {
        DateFormat df = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss aaa");
        Date hoy = Calendar.getInstance().getTime();
        String fecha = df.format(hoy);
        return fecha;
    }

    /*
     * Función SetRespuestaProhibida(): Arma el mensaje de respuesta para
     * páginas prohibidas.
     */
    public void SetRespuestaProhibida() {
        try {
            if (server_ip != null) {
                String header = "HTTP/1.1 403 FORBIDDEN\n"
                        + "Content-Type: text/html\n\n";
                salida.write(header.getBytes());

                // Se obtiene el texto del archivo html de páginas prohibidas
                File pagina = new File("Resources/prohibida.html");
                FileInputStream fileinput = null;
                BufferedInputStream mybuffer = null;

                fileinput = new FileInputStream(pagina);
                mybuffer = new BufferedInputStream(fileinput);

                EnviarRespuesta(mybuffer);

                //Escribe en el log
                String log_entry = "Prohibida "
                        + server_ip + " " + server_url.getPath()
                        + " " + self_ip
                        + " " + getDate();
                EscribirLog(log_entry);
            }

        } catch (IOException e) {
            System.err.println("Error armando respuesta de pagina prohibida");
        }
    }


    /*
     * Función SetRespuestaHTTP(): Arma el mensaje de respuesta para
     * páginas válidas.
     */
    public synchronized void SetRespuestaHTTP(String request) {
        try {
            server_socket = new Socket(server_ip, 80);
            PrintWriter salida_server = new PrintWriter(server_socket.getOutputStream(), true);
            BufferedInputStream in = new BufferedInputStream(server_socket.getInputStream());

            salida_server.println(request);

            try {
                Thread.sleep(50);
            } catch (Exception e) {
                System.err.println("Error durmiendo thread");
                e.printStackTrace();
            }
            EnviarRespuesta(in);

            //Escribe en el log
            String log_entry = "Permitida "
                    + server_ip + " " + server_url.getPath()
                    + " " + self_ip
                    + " " + getDate();
            EscribirLog(log_entry);

        }catch (java.net.SocketException ex) {
            //Por ahora, se ignoran las excepciones del socket.
        }catch (IOException e) {
            System.err.println("Error armando request del proxy");
        }

    }

    /*
     * Función EnviarRespuesta(): Lee el Stream de entrada y escribe los datos
     * recibidos en el socket del cliente.
     */
    
    public synchronized void EnviarRespuesta(BufferedInputStream in) {
        try {
            byte[] buffer = new byte[10];
            int input;
            while ((input = in.read(buffer)) >= 0) {
                salida.write(buffer);
                buffer = new byte[10];
                salida.flush();
            }
            //salida.flush();
            salida.close();
        }catch(java.net.SocketException ex){
        
        }
        catch (IOException e) {
            System.err.println("Error enviando respuesta al cliente");
            e.printStackTrace();
        }
    }

    /*
     * Función EscribirLog(String s): Escribe una nueva entrada en el log
     */
    public synchronized void EscribirLog(String s) {
        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter("Resources/log.txt", true));
            writer.write(s);
            writer.newLine();
            writer.flush();
            writer.close();
        } catch (IOException e) {
            System.err.println("Error de escritura en el log");
        }
    }

    // Programa principal que corre el thread
    public synchronized void run() {
        try {
            String request = LeerRequest();
            boolean esta_prohibida = BuscarEnProhibidas(request);
            if (esta_prohibida) {
                SetRespuestaProhibida();
            } else {
                if (server_url.getProtocol().equals("http")) {
                    SetRespuestaHTTP(request);
                } else {
                    //SetRespuestaHTTPS();
                }
            }
            client_socket.close();
        } catch (IOException e) {
            System.err.print("Error cerrando el socket");
        }
    }
}
